package org.example.redisson;

import cn.hutool.core.lang.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * @Classname RedisLockImpl
 * @Description 封装redis分布锁加锁解锁
 * @Date 2021/3/24 4:16 下午
 * 上述解决分布式锁失效的问题在分布式锁领域有一个专业的术语叫做 “异步续命” 。需要注意的是：当业务代码执行完毕后，
 * 我们需要停止更新锁超时时间的线程。所以，这里，我对程序的改动是比较大的，首先，将更新锁超时的时间任务重新定义为一个
 * UpdateLockTimeoutTask类，并将uuid和StringRedisTemplate注入到任务类中，在执行定时更新锁超时时间时，
 * 首先将当前线程保存到Redis中，其中Key为传递进来的uuid。
 * 在首先获取分布式锁后，重新启动线程，并将uuid和StringRedisTemplate传递到任务类中执行任务。当业务代码执行完毕后，
 * 调用releaseLock()方法释放锁时，我们会通过uuid从Redis中获取更新锁超时时间的线程id，
 * 并通过线程id获取到更新锁超时时间的线程，调用线程的interrupt()方法来中断线程。
 * 此时，当分布式锁释放后，更新锁超时的线程就会由于线程中断而退出了。
 * @Created by xiangjd
 */
public class RedisLockImpl5 implements RedisLock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //把加锁和解锁放在同一个线程里,防止不同线程之间串改
    private ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    private ThreadLocal<Integer> threadLocalInteger = new ThreadLocal<Integer>();

    @Override
    public boolean tryLock(String key, long timeout, TimeUnit unit) {
        boolean isLock = false;
        if (threadLocal.get() == null) {
            String uuid = UUID.randomUUID().toString();
            threadLocal.set(uuid);
            isLock = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
            //如果获取锁失败，则自旋获取锁，直到成功
            if (!isLock) {
                for (; ; ) {
                    isLock = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);
                    if (isLock) {
                        break;
                    }
                }
            }
            //启动新线程来执行定时任务，更新锁过期时间
            new Thread(new UpdateLockTimeoutTask(uuid, stringRedisTemplate, key)).start();

        } else {
            isLock = true;
        }
        if (isLock) {
            Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();
            threadLocalInteger.set(count++);
        }
        return isLock;
    }

    @Override
    public void releaseLock(String key) {
        //当前线程的uuid和redis中的uuid一样才能进行删除
        String uuid = stringRedisTemplate.opsForValue().get(key);
        if (threadLocal.get().equals(uuid)) {
            Integer count = threadLocalInteger.get();
            //计数器减为0时释放锁
            if (count == 0 || --count <= 0) {
                //获取异步线程续期key的thread
                String threadId = stringRedisTemplate.opsForValue().get(uuid);
                Thread threadByThreadId = ThreadUtils.getThreadByThreadId(Long.parseLong(threadId));
                if (threadByThreadId != null) {
                    //获取更新锁超时时间的线程并中断
                    threadByThreadId.interrupt();
                    stringRedisTemplate.delete(uuid);
                }

                stringRedisTemplate.delete(key);
            }

        }
    }
}
